#!/usr/bin/env python
#-*- coding:utf-8 -*-

__author__ = "Pierre Bienaime"
__author_email__ = "pbienaim@gmail.com"
__date__ = "2012-11-21"

import sys, string
import pynarcissus.jsparser as js

INDENT="  "
terminal_nodes = [ 'BREAK', 'CONTINUE', 'DEBUGGER', 'FALSE', 'NULL', 'NUMBER', 'REGEXP',
                   'STRING', 'THIS', 'TRUE']
                   
nonterminal_nodes = [ 'AND', 'ARRAY_INIT', 'ASSIGN', 'BITWISE_AND', 'BITWISE_NOT', 'BITWISE_OR',
                      'BITWISE_XOR', 'BLOCK', 'CALL', 'COMMA', 'CONST', 'DECREMENT', 'DELETE',
                      'DIV', 'DOT', 'EQ', 'GE', 'GROUP', 'GT', 'HOOK', 'IN', 'INCREMENT', 'INDEX',
                      'INSTANCEOF', 'LE', 'LIST', 'LSH', 'LT', 'MINUS', 'MOD', 'MUL', 'NE', 'NEW',
                      'NEW_WITH_ARGS', 'NOT', 'OBJECT_INIT', 'OR', 'PLUS', 'PROPERTY_INIT', 'RSH',
                      'SCRIPT', 'STRICT_EQ', 'STRICT_NE', 'TYPEOF', 'UNARY_MINUS', 'UNARY_PLUS',
                      'URSH', 'VAR', 'VOID']

subparts = { 
    "CASE": ["caseLabel", "statements"],
    "CATCH": ["block", "guard"],
    "DEFAULT": ["statements"],
    "DO": ["body", "condition"],
    "IDENTIFIER": ["initializer"],
    "IF": ["condition", "thenPart", "elsePart"],
    "FOR": ["body", "setup", "condition", "update"],
    "FOR_IN": ["body", "iterator", "object"],
    "FUNCTION": ["body"],
    "LABEL": ["statement"],
    "RETURN": ["value"],
    "SEMICOLON" : ["expression"],
    "SWITCH": ["cases", "discriminant"],
    "THROW": ["exception"],
    "TRY": ["catchClauses", "tryBlock", "finallyBlock"],
    "WITH": ["body", "object"],
    "WHILE": ["condition","body"]
    }
    
type_dict = {
    "PLUS":"+",
    "LT":"<",
    "EQ":"==",
    "AND":"&&",
    "OR":"||",
    "MINUS":"-",
    "MUL":"*",
    "LE":"<=",
    "NE":"!=",
    "STRICT_EQ":"===",
    "DIV":"/",
    "GE":">=",
    "INSTANCEOF":"instanceof",
    "IN":"in",
    "GT":">",
    "BITWISE_OR":"|",
    "BITWISE_AND":"&",
    "BITWISE_XOR":"^",
    "STRICT_NE":"!==",
    "LSH":"<<",
    "RSH":">>",
    "URSH":">>>",
    "MOD":"%"
}

def indent(n, i=""):
    if n.type == "ARRAY_INIT":
        return "[%s]" % ",".join([indent(node, i) for node in n])

    elif n.type == "ASSIGN":
        op = "="
        if n.value != "=":
            op = n.value + op
        return "%s %s %s" % (n[0].value, op, indent(n[1], i))

    elif n.type == "BLOCK":
        if len(n) > 1:
            r = "{{{0}\n{i}}}".format("".join("\n"+i + indent(node, i+INDENT) for node in n), i=i[len(INDENT):])
        elif len(n) == 1:
            r = indent(n[0], i)
        if r:
            return r
        return ""

    elif n.type in ("BREAK", "CONTINUE"):
        if hasattr(n,"label"):
            return "%s %s" % (n.value, n.label)
        return n.value

    elif n.type == "CALL":
#            return "(CALL%s %s%s)" % (props(), o(n[0], i, c), o(n[1], i, c))
        return "%s(%s)" % (indent(n[0], i), indent(n[1], i))

#        elif n.type == "CASE":
#            check(attrs=["caseLabel","statements"])
#            return "(CASE%s %s %s)" % (props(), o(n.caseLabel,i,c, ex=ex, ev=ev),
#                    o(n.statements,i,c, ex=ex, ev=ev))

#        elif n.type == "CATCH":
#            check(attrs=["block","guard","varName"])
#            if n.guard is not None:
#                return "(GUARDED-CATCH%s %s %s %s)" % (props(), n.varName,
#                        o(n.guard,i,c, ex=ex, ev=ev), o(n.block,i,c, ex=ex, ev=ev))
#            return "catch({0}) {{\n{1}\n}}".format(n.varName, o(n.block,i,c, ex=ex, ev=ev))

#        elif n.type == "COMMA":
#            check(subnodes=2)
#            return "(COMMA%s %s %s)" % (props(), o(n[0],i,c, ex=ex, ev=ev), o(n[1],i,c, ex=ex, ev=ev))

#        elif n.type == "DEBUGGER":
#            return "(DEBUGGER%s)" % props()

#        elif n.type == "DEFAULT":
#            check(attrs=["statements"])
#            return "(DEFAULT%s %s)" % (props(), o(n.statements,i,c, ex=ex, ev=ev))

    elif n.type in ("DELETE", "TYPEOF", "NEW", "UNARY_MINUS", "NOT",
            "VOID", "BITWISE_NOT", "UNARY_PLUS"):
        return "%s %s" % (n.value, indent(n[0], i))

#        elif n.type == "DO":
#            check(attrs=["body", "condition", "isLoop"])
#            assert n.isLoop
#            return "(DO-WHILE%s %s %s)" % (props(), o(n.condition,i,c, ex=ex, ev=ev),
#                    o(n.body,i,c, ex=ex, ev=ev))

    elif n.type == "DOT":
        return "%s.%s" % (indent(n[0], i), indent(n[1], i))

    elif n.type == "FUNCTION":
        if hasattr(n, "name"):
            return "function {0} ({1}) {{\n{i}{2}\n{i}}}".format(n.name, ", ".join(n.params), indent(n.body, i+INDENT), i=i)
        return "function({0}) {{\n{i}{1}\n{i}}}".format(", ".join(n.params), indent(n.body, i+INDENT), i=i)

    elif n.type == "FOR":
        assert n.isLoop
        setup = condition = update = body = ""
        if n.setup:
            setup = indent(n.setup, i)
        if n.condition:
            condition = indent(n.condition, i)
        if n.update:
            update = indent(n.update, i)
        if n.body:
            body = indent(n.body, i+INDENT)
        return "for ({0}; {1}; {2}) {3}".format(setup, condition, update, body)

#        elif n.type == "FOR_IN":
#            check(attrs=["body","iterator","object","isLoop","varDecl"])
#            assert n.isLoop
#            s = "(FOR-IN"
#            if n.varDecl:
#                assert n.varDecl.type == "VAR"
#                assert len(n.varDecl) == 1
#                assert n.varDecl[0].type == "IDENTIFIER"
#                assert n.varDecl[0].value == n.iterator.value
#                s += "-VAR"
#            return s + "%s %s %s %s)" % (props(), o(n.iterator,i,c, ex=ex, ev=ev),
#                    o(n.object,i,c, ex=ex, ev=ev), o(n.body,i,c, ex=ex, ev=ev))

    elif n.type == "GROUP":
        return indent(n[0], i)

#        elif n.type == "HOOK":
#            check(subnodes=3)
#            return "(TERNARY%s %s %s %s)" % (props(),o(n[0],i,c, ex=ex, ev=ev),o(n[1],i,c, ex=ex, ev=ev),
#                    o(n[2],i,c, ex=ex, ev=ev))

    elif n.type == "IDENTIFIER":
        if hasattr(n,"initializer"):
            return "%s = %s" % (n.value, indent(n.initializer, i))
        return str(n.value)

    elif n.type == "IF":
        if n.elsePart:
            return "if ({0}) {1}\n{i}else {2}".format(indent(n.condition, i), indent(n.thenPart, i+INDENT,), indent(n.elsePart, i+INDENT), i=i)
        return "if ({0}) {1}".format(indent(n.condition, i), indent(n.thenPart, i+INDENT))


    elif n.type in ["INCREMENT", "DECREMENT"]:
        op = "--"
        if n.type == "INCREMENT":
            op = "++"
        if hasattr(n, "postfix") and n.postfix:
            return indent(n[0], i) + op
        return op + indent(n[0], i)

    elif n.type == "INDEX":
        return "%s[%s]" % (indent(n[0], i), indent(n[1], i))

#
#        elif n.type == "LABEL":
#            check(attrs=["label","statement"])
#            return "(LABELED-STATEMENT%s %s\n  %s%s)" % (props(), n.label, i,
#                    o(n.statement, i+"  ", c, ex=ex, ev=ev))

    elif n.type == "LIST":
        return ', '.join(indent(node, i) for node in n)

    elif n.type == "NEW_WITH_ARGS":
        return "new %s(%s)" % (indent(n[0], i), indent(n[1], i)) 

    elif n.type in ("NUMBER", "TRUE", "FALSE", "THIS", "NULL"):
        return str(n.value)

#        elif n.type == "OBJECT_INIT":
#            check(subnodes=len(n))
#            if len(n) > 0:
#                return ("(OBJECT_INIT%s\n  " % props() + i +
#                        ("\n  "+i).join(o(x,i+"  ",c, ex=ex, ev=ev) for x in n) + ")")
#            return "(OBJECT-INIT%s)" % props()

    elif n.type in ["PLUS", "LT", "EQ", "AND", "OR", "MINUS", "MUL", "LE",
            "NE", "STRICT_EQ", "DIV", "GE", "INSTANCEOF", "IN", "GT",
            "BITWISE_OR", "BITWISE_AND", "BITWISE_XOR", "STRICT_NE", "LSH",
            "RSH", "URSH", "MOD"]:
        if n.type in ["PLUS", "MINUS", "MUL", "DIV"]:
            return "%s%s%s" % (indent(n[0], i), type_dict[n.type], indent(n[1], i))
        return "%s %s %s" % (indent(n[0], i), type_dict[n.type], indent(n[1], i))

#        elif n.type == "PROPERTY_INIT":
#            check(subnodes=2)
#            return "(PROPERTY%s %s %s)" % (props(), o(n[0],i,c, ex=ex, ev=ev), o(n[1],i,c, ex=ex, ev=ev))

#        elif n.type == "REGEXP":
#            return "(REGEXP%s %r %r)" % (props(), n.value["regexp"],
#                    n.value["modifiers"])

    elif n.type == "RETURN":
        return "return %s" % indent(n.value, i)

    elif n.type == "SCRIPT":
        if len(n) > 0:
            r = i + ("\n"+i).join(indent(node, i) for node in n)
            return r
        return ""

    elif n.type == "SEMICOLON":
        if n.expression:
            return indent(n.expression, i) + ";"
        return ""

    elif n.type == "STRING":
        return repr(n.value)

#        elif n.type == "SWITCH":
#            check(attrs=["cases", "defaultIndex", "discriminant"])
#            assert (n.defaultIndex == -1 or
#                    n.cases[n.defaultIndex].type == "DEFAULT")
#            return "(SWITCH%s %s\n  %s%s)" % (props(), o(n.discriminant,i,c, ex=ex, ev=ev), i,
#                    ("\n  "+i).join(o(x,i+"  ",c, ex=ex, ev=ev) for x in n.cases))

#        elif n.type == "THROW":
#            check(attrs=["exception"])
#            return "(THROW%s %s)" % (props(), o(n.exception,i,c, ex=ex, ev=ev))

#        elif n.type == "TRY":
#            check(attrs=["catchClauses","tryBlock"], optattrs=["finallyBlock"])
#            if hasattr(n,"finallyBlock"):
#                return "(TRY-FINALLY%s\n  " % props() + i + ("\n  "+i).join(
#                        [o(n.tryBlock,i+"  ",c, ex=ex, ev=ev)] + [o(x,i+"  ",c, ex=ex, ev=ev)
#                        for x in n.catchClauses] + \
#                        [o(n.finallyBlock,i+"  ",c, ex=ex, ev=ev)]) + ")"
#            return "try {\n" + ("\n  "+i).join([o(n.tryBlock,i+"  ",c, ex=ex, ev=ev)]) +"\n}\n" + "".join([o(x,i+"  ",c, ex=ex, ev=ev) for x in n.catchClauses])

    elif n.type in ["VAR", "CONST"]:
        return "%s %s" %( n.value, ", ".join(indent(node, i) for node in n))

#        elif n.type == "WITH":
#            check(attrs=["body", "object"])
#            sys.stderr.write("WARNING: A bad person wrote the code being "
#                    "parsed. Don't use 'with'!\n")
#            return "(WITH%s %s %s)" % (props(), o(n.object,i,c, ex=ex, ev=ev), o(n.body,i,c, ex=ex, ev=ev))

#        elif n.type == "WHILE":
#            check(attrs=["condition","body","isLoop"])
#            assert n.isLoop
#            return "(WHILE%s %s\n  %s%s)" % (props(), o(n.condition,i,c, ex=ex, ev=ev), i,
#                    o(n.body, i+"  ", c, ex=ex, ev=ev))

    else:
        print "Fail to: " + n.type
        print n


def normalize(n):   
    # Found an operator    
    if n.type in ["PLUS", "MINUS", "MUL", "DIV"]:
        if n[0].type == n[1].type:
            if n[0].type == "NUMBER":
                if n.type == "PLUS":
                    r = n[0].value + n[1].value
                elif n.type == "MINUS":
                    r = n[0].value - n[1].value
                elif n.type == "MUL":
                    r = n[0].value * n[1].value
                else:
                    r = float(n[0].value) / float(n[1].value)
                    if r.is_integer() and all(type(i) == type(1) for i in [n[0].value, n[1].value]):
                        r = int(r)
                print "{0} {1} {2} = {3}".format(n[0].value, n.type, n[1].value, r)
                return (n.start, n[1].end, str(r))
            elif n[0].type == "STRING":
                if n.type == "PLUS":
                    r = repr(n[0].value + n[1].value)
                    print "{0} {1} {2} = {3}".format(n[0].value, n.type, n[1].value, r)
                    return (n.start, n[1].end, str(r))
    unary = ["UNARY_PLUS", "UNARY_MINUS"]
    if n.type in unary:
        if n[0].type in unary:
            if n[0][0].type not in unary:
                if n[0].type == n.type:
                    return (n.start, n[0][0].start, "+")
                return (n.start, n[0][0].start, "-")
    if n.type == "UNARY_PLUS" and n[0].type == "NUMBER":
        return (n.start, n[0].start, "")
        
    
    # Found String["fromCharCode"](42,1337,...)
    if n.type == "CALL" and n[0].type == "INDEX" and n[0][0].type == "IDENTIFIER" \
            and n[0][0].value == "String" and n[0][1].type == "STRING" and n[0][1].value == "fromCharCode" \
            and n[1].type == "LIST":
        if all(x.type == "NUMBER" for x in n[1]):
            r = ""
            error = False
            for x in n[1]:
                try:
                    r += chr(x.value)
                except ValueError:
                    error = True
            if not error:
                return (n.start, n.end, repr(r))
    # Found unsecape('%66%83%...)
    if n.type == "CALL" and n[0].type == "IDENTIFIER" and n[0].value == "unescape" and n[1].type == "LIST":
        r = unescape(n[1][0].value)
        if is_printable(r):
            r = r.encode("utf8")
            return (n.start, n.end, repr(r))
    
    if n.type in nonterminal_nodes:
        for node in n:
            r = normalize(node)
            if r:
                return r
    elif n.type in subparts:
        for part in subparts[n.type]:
            if hasattr(n, part):
                node = getattr(n, part)
                if node:
                    r = normalize(node)
                    if r:
                        return r
    elif n.type in terminal_nodes:
        return None
  
def rewrite(content, changes):
    begin, end, new = changes
    return content[:begin] + new + content[end:]


def main_loop(content):
    parsed_content = js.parse(content)
    for c in parsed_content:
        change = normalize(c)
        if change:
            return rewrite(content, change)
            
            
# TOOLS                 
def unescape(s):
    i = 0
    l = len(s)
    r = u""
    while i < l:
        if s[i] == "%" and i+2 <= l:
            if s[i+1] == "u" and i+5 <= l:
                try:
                    c = int(s[i+2:i+6], 16)
                except ValueError:
                    r += s[i:i+2]
                    i += 2
                    continue
                r += unichr(c)
                i += 6
            else:
                try:
                    c = int(s[i+1:i+3], 16)
                except ValueError:
                    r += s[i]
                    i += 1
                    continue
                r += unichr(c)
                i += 3
        else:
            r += s[i]
            i += 1
    return r

def is_printable(s):
    return all(x in string.printable for x in s)

def main():
    content = open(sys.argv[1]).read()
    content = indent(js.parse(content))
    while main_loop(content):
        content = main_loop(content)
        print content
    print "finished"
    print content
    
if __name__ == '__main__':
    main()
  
    
